// =============================================================================
// PROJECT CHRONO - http://projectchrono.org
//
// Copyright (c) 2022 projectchrono.org
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file at the top level of the distribution and at
// http://projectchrono.org/license-chrono.txt.
//
// =============================================================================
// Radu Serban,  Hammad Mazhar
// =============================================================================

#ifndef CH_VISUAL_SYSTEM_OPENGL_H
#define CH_VISUAL_SYSTEM_OPENGL_H

#include "chrono/ChConfig.h"
#include "chrono/assets/ChVisualSystem.h"

#include "chrono_opengl/core/ChApiOpenGL.h"
#include "chrono_opengl/ChOpenGLViewer.h"

namespace chrono {

#ifdef CHRONO_MULTICORE
class ChSystemMulticore;
#endif

/// Namespace with classes for the OpenGL module.
namespace opengl {

/// Interface of an object which can receive events.
class CH_OPENGL_API ChOpenGLEventCB {
  public:
    virtual ~ChOpenGLEventCB() {}

    /// GLFW callback to handle keyboard events.
    /// Return 'true' if the event is completely done and no further processing is to occur.
    virtual bool CallbackKeyboard(GLFWwindow* window, int key, int scancode, int action, int mode) = 0;

    /// GLFW callback to handle mouse button events.
    /// Return 'true' if the event is completely done and no further processing is to occur.
    virtual bool CallbackMouseButton(GLFWwindow* window, int button, int action, int mods) = 0;

    /// GLFW callback to handle events generated by changes in mouse position.
    /// Return 'true' if the event is completely done and no further processing is to occur.
    virtual bool CallbackMousePos(GLFWwindow* window, double x, double y) = 0;
};

/// Base class for a particle rendering discriminator.
class CH_OPENGL_API ChOpenGLParticleCB {
  public:
    virtual ~ChOpenGLParticleCB() {}

    /// Callback for selecting particles to be rendered.
    /// Return 'true' to render the particle at the specified location.
    virtual bool Render(const ChVector<>& pos) const = 0;
};

/// OpenGL-based Chrono run-time visualization system.
class CH_OPENGL_API ChVisualSystemOpenGL : virtual public ChVisualSystem {
  public:
    ChVisualSystemOpenGL();
    virtual ~ChVisualSystemOpenGL();

    /// Set the window size (default 640x480).
    /// Must be called before Initialize().
    void SetWindowSize(unsigned int width, unsigned int height);

    /// Set the window title (default "").
    /// Must be called before Initialize().
    void SetWindowTitle(const std::string& win_title);

    /// Set camera vertical direction (default CameraVerticalDir::Z).
    void SetCameraVertical(CameraVerticalDir vert);

    /// Set camera vertical direction (default (0,0,1)).
    void SetCameraVertical(const ChVector<>& up);

    /// Set camera scale and clip distances. 
    void SetCameraProperties(float scale = 0.5f,            ///< zoom level (default 0.5)
                             float near_clip_dist = 0.1f,   ///< near clipping distance (default 0.1)
                             float far_clip_dist = 1000.0f  ///< far clipping distance (default 1000)
    );

    /// Set render mode for solid objects (default: SOLID)
    void SetRenderMode(RenderMode mode);

    /// Set render mode for particle systems (default: POINTS)
    void SetParticleRenderMode(float radius, RenderMode mode);

    /// Attach a Chrono system to the run-time visualization system.
    /// ChVisualSystemOpenGL allows simultaneous rendering of multiple Chrono systems.
    virtual void AttachSystem(ChSystem* sys) override;

    /// Attach a user-defined text renderer for stats overlay.
    /// This overwrites the default stats overlay renderer.
    void AttachStatsRenderer(std::shared_ptr<ChOpenGLStats> renderer);

    /// Enable/disable stats overlay display (default: true).
    void EnableStats(bool state) { render_stats = state; }

    /// Initialize the visualization system.
    /// This creates the Irrlicht device using the current values for the optional device parameters.
    virtual void Initialize();

    /// Set camera position and look-at point.
    /// The camera rotation/pan is controlled by mouse left and right buttons, the zoom is controlled by mouse wheel or
    /// rmb+lmb+mouse, the position can be changed also with keyboard up/down/left/right arrows, the height can be
    /// changed with keyboard 'PgUp' and 'PgDn' keys. Optional parameters are position and target.
    void SetCameraPosition(const ChVector<>& pos, ChVector<> targ = VNULL);

    /// Attach a custom event receiver to the application.
    void AddUserEventReceiver(ChOpenGLEventCB* receiver) { user_receivers.push_back(receiver); }

    /// Attach a custom particle rendering selector.
    void AttachParticleSelector(std::shared_ptr<ChOpenGLParticleCB> selector) { particle_selector = selector; }

    /// Process all visual assets in the associated ChSystem.
    /// This function is called by default by Initialize(), but can also be called later if further modifications to
    /// visualization assets occur.
    virtual void BindAll() override;

    /// Process the visual assets for the spcified physics item.
    /// This function must be called if a new physics item is added to the system or if changes to its visual model
    /// occur after the call to Initialize().
    virtual void BindItem(std::shared_ptr<ChPhysicsItem> item) override;

    /// Run the Irrlicht device.
    /// Returns `false` if the device wants to be deleted.
    bool Run();

    /// Clean the canvas at the beginning of each animation frame.
    virtual void BeginScene(bool backBuffer = true, bool zBuffer = true, ChColor color = ChColor(0, 0, 0));

    /// Draw all 3D shapes and GUI elements at the current frame.
    /// This function is typically called inside a loop such as
    /// <pre>
    ///    while(vis->Run()) {...}
    /// </pre>
    virtual void Render();

    /// End the scene draw at the end of each animation frame.
    virtual void EndScene();

    /// Create a snapshot of the last rendered frame and save it to the provided file.
    /// The file extension determines the image format.
    virtual void WriteImageToFile(const std::string& filename) override;

    /// Callback wrapper for steps of the render loop. Works with Emscripten.
    static void WrapRenderStep(void* stepFunction);

  private:
    /// Perform necessary setup operations at the beginning of a time step.
    virtual void OnSetup(ChSystem* sys) override;

    /// Perform necessary update operations at the end of a time step.
    virtual void OnUpdate(ChSystem* sys) override;

    /// Remove all visualization objects from this visualization system.
    virtual void OnClear(ChSystem* sys) override;

    /// Provide the version of the OpenGL context along with driver information.
    static void GLFWGetVersion(GLFWwindow* main_window);

    /// GLFW error callback, returns error string.
    static void CallbackError(int error, const char* description);

    /// GLFW close callback, called when window is closed.
    static void CallbackClose(GLFWwindow* window);

    /// GLFW reshape callback, handles window resizing events.
    static void CallbackReshape(GLFWwindow* window, int w, int h);

    /// GLFW keyboard callback, handles keyboard events.
    static void CallbackKeyboard(GLFWwindow* window, int key, int scancode, int action, int mode);

    /// GLFW mouse button callback, handles mouse button events.
    static void CallbackMouseButton(GLFWwindow* window, int button, int action, int mods);

    /// GLFW mouse position callback, handles events generated by changes in mouse position.
    static void CallbackMousePos(GLFWwindow* window, double x, double y);

    GLFWwindow* window;                             ///< GL window
    ChOpenGLViewer* viewer;                         ///< viewer responsible for all rendering
    std::shared_ptr<ChOpenGLStats> stats_renderer;  ///< stats overlay renderer

    std::string m_win_title;  ///< window title
    int m_width;              ///< window width
    int m_height;             ///< window height

    RenderMode m_solid_mode;            ///< render mode for solids
    RenderMode m_particle_render_mode;  ///< render mode for particles
    float m_particle_radius;            ///< render radius for particles

    ChVector<> m_camera_pos;   ///< camera position
    ChVector<> m_camera_targ;  ///< camera look-at point
    ChVector<> m_camera_up;    ///< camera vertical
    float m_camera_scale;      ///< camera move scale
    float m_camera_near;       ///< camera near clip distance
    float m_camera_far;        ///< camera far clip distance

    bool render_stats;
    std::vector<ChOpenGLEventCB*> user_receivers;
    std::shared_ptr<ChOpenGLParticleCB> particle_selector;

#ifdef CHRONO_MULTICORE
    std::vector<ChSystemMulticore*> m_systems_mcore;
#endif

    friend class ChOpenGLViewer;
};

}  // namespace opengl
}  // namespace chrono

#endif
